package com.osdp.common.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.osdp.common.constant.CommonConstant;
import com.osdp.common.base.JWTInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.lang.JoseException;
import org.springframework.security.core.context.SecurityContextHolder;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

/**
 * 生成jwt方法类,RS256(采用SHA-256 的 RSA 签名)加密
 * RS256不对称加密，需要用public cert来验证
 */
@Slf4j
public class TokenUtil {
    private TokenUtil(){
        throw new AssertionError("TokenUtil 为工具类不允许初始化");
    }
    /**
     * 生成jwt,RS256(采用SHA-256 的 RSA 签名)加密
     * @return String
     * @throws Exception
     */
    public static String createToken(JWTInfo info) throws Exception {
        PrivateKey privateKey = RSAUtil.getPrivateKey();
        if(null == privateKey){
            throw new RuntimeException("获取RSA密钥失败");
        }
        final JwtClaims claims = new JwtClaims();
        String userJson = JSONObject.toJSONString(info);// 放入payload用户信息，包括角色、权限
        claims.setClaim("info", userJson);
        claims.setSubject(info.getUserName());
        claims.setAudience(CommonConstant.JWT_SIGN_AUDIENCE);//用于验证签名是否合法，验证方必须包含这些内容才验证通过
        claims.setExpirationTimeMinutesInTheFuture(30 * 24 * 60);//指定过期时间
        claims.setIssuedAtToNow();
        // Generate the payload
        final JsonWebSignature jws = new JsonWebSignature();
        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
        jws.setPayload(claims.toJson());
        jws.setKeyIdHeaderValue(UUID.randomUUID().toString());
        // Sign using the private key
        jws.setKey(privateKey);
        try {
            return jws.getCompactSerialization();
        } catch (JoseException e) {
            log.error(e.getMessage(),e);
            return null;
        }
    }

    /**
     * 验证jwt
     * @param token String
     * @return JwtClaims
     * @throws Exception
     */
    public static JwtClaims verifyToken(String token) throws Exception {
        PublicKey publicKey = RSAUtil.getPublicKey(); //获取公共key,如：idsrv4.crt/.pem
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setRequireExpirationTime()
                .setVerificationKey(publicKey)
                .setExpectedAudience(CommonConstant.JWT_SIGN_AUDIENCE)//签名必须包含此内容，否则校验不通过
                .build();

        return jwtConsumer.processToClaims(token);
    }

    /**
     * 校验token，返回用户信息
     * @param token String
     * @return JWTInfo
     */
    public static JWTInfo verify(String token){
        try {
            JwtClaims jwtClaims = verifyToken(token);
            String userJson = (String)jwtClaims.getClaimValue("info");
            JWTInfo info = JSON.parseObject(userJson, new TypeReference<JWTInfo>() {});
            return info;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据token获取jwt信息
     * @param token String
     * @return JWTInfo
     */
    public static JWTInfo getJwtInfo(String token){
        JWTInfo info = verify(token);
        if(null == info){
            throw new RuntimeException("token获取payload信息失败");
        }
        return info;
    }

    /**
     * 从上下文中获取jwtinfo
     * @return JWTInfo
     */
    public static JWTInfo getJwtInfo(){
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if(principal instanceof JWTInfo){
            return (JWTInfo) principal;
        }
        throw new RuntimeException("无法从上下文中获取到jwtinfo");
    }
    /**
     * 获取租户id
     * @param token
     * @return String
     */
    public static String getTenantId(String token){
        JWTInfo info = verify(token);
        if(null == info){
            throw new RuntimeException("token获取payload信息失败");
        }
        return info.getNowTenant();
    }

    /**
     * 获取token, 从Request ,如果没有，从codkie里面获取
     * @param request  HttpServletRequest
     * @return String
     */
    public static String getToken(HttpServletRequest request){
        String token = null;
        Cookie[] cookies = request.getCookies();
        if(null != cookies && cookies.length != 0){
            Optional<String> tokenOpt = Arrays.stream(cookies).filter(c -> StringUtils.equals(CommonConstant.HEADER_ACCESS_TOKEN, c.getName())).map(c -> c.getValue()).findFirst();
            if(tokenOpt.isPresent()){
                token = tokenOpt.get();
            }
        }
        if(StringUtils.isEmpty(token)){
            token = request.getHeader(CommonConstant.HEADER_ACCESS_TOKEN);
        }
        if(StringUtils.isEmpty(token)){
            throw new RuntimeException("请求域中无token信息");
        }
        return token;
    }

    /**
     * 获取用户名
     * @param token String
     * @return String
     */
    public static String getUserName(String token){
        JWTInfo info = verify(token);
        if(null == info){
            throw new RuntimeException("token获取payload信息失败");
        }
        return info.getUserName();
    }

    /**
     * 获取用户Id
     * @param token String
     * @return String
     */
    public static String getUserId(String token){
        JWTInfo info = verify(token);
        if(null == info){
            throw new RuntimeException("token获取payload信息失败");
        }
        return info.getUserId();
    }

    /**
     * 获取角色信息
     * @param token String
     * @return List<String>
     */
    public static List<String> getRoles(String token){
        JWTInfo info = verify(token);
        if(null == info){
            throw new RuntimeException("token获取payload信息失败");
        }
        return info.getRoles();
    }
}
